package service

import (
	"context"
	"fmt"
	"inventory/api/inventory"
	"inventory/api/prod"
	"inventory/internal/constant"
	"inventory/internal/model"
	"inventory/pkg/ecode"
	"inventory/pkg/util"
	"strings"
	"time"
)

// GetBom 获取bom配置
func (s *Service) GetBom(ctx context.Context, req *inventory.GetBomRequest) (resp *inventory.GetBomResponse, err error) {
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var prods []*inventory.ProdBomView
	var total int64
	if total, prods, err = s.downstream.FetchProductBom(ctx, req.WebToken.SgId, req.PStoreId, req.PStoreId,
		req.PageNo, req.PageSize,
		req.IsConfigured, uint32(req.ProdNature), constant.FetchModeFollowPStoreID, req.Query, nil); err != nil {
		return
	}
	var storageKeys []string
	skuIDStorageKey := make(map[uint32]map[uint32]string)
	for _, p := range prods {
		skuIDStorageKey[p.Id] = make(map[uint32]string)
		for _, sku := range p.Skus {
			sk := fmt.Sprintf("%d-%d", p.Id, sku.Id)
			skuIDStorageKey[p.Id][sku.Id] = sk
			storageKeys = append(storageKeys, sk)
		}
	}
	var bom []*model.BomConfig
	if bom, err = s.dao.FindBomByStorageKeys(ctx, storageKeys); err != nil {
		return
	}
	storageKeyBom := make(map[string][]*model.BomConfig)
	var materialIDs []uint64
	for _, b := range bom {
		storageKeyBom[b.StorageKey] = append(storageKeyBom[b.StorageKey], b)
		materialIDs = append(materialIDs, b.MaterialID)
	}
	var materials []*model.Material
	if materials, err = s.dao.FindMaterialsByIDs(ctx, materialIDs); err != nil {
		return
	}
	materialMap := make(map[uint64]*model.Material)
	var unitIDs []uint32
	for _, m := range materials {
		materialMap[m.ID] = m
		unitIDs = append(unitIDs, m.Unit)
	}
	var units []*model.Unit
	if units, err = s.dao.FindUnitsByIDs(ctx, req.WebToken.SgId, req.PStoreId, unitIDs); err != nil {
		return
	}
	unitNames := make(map[uint32]string, len(units))
	for _, u := range units {
		unitNames[u.ID] = u.Name
	}
	for _, p := range prods {
		for _, sku := range p.Skus {
			sku.Bom = make([]*inventory.BomView, len(storageKeyBom[skuIDStorageKey[p.Id][sku.Id]]))
			for i, b := range storageKeyBom[skuIDStorageKey[p.Id][sku.Id]] {
				sku.Bom[i] = &inventory.BomView{
					Id:       b.MaterialID,
					Name:     materialMap[b.MaterialID].Name,
					Unit:     unitNames[materialMap[b.MaterialID].Unit],
					Quantity: b.Quantity,
				}
			}
		}
	}
	resp = &inventory.GetBomResponse{
		Total: uint32(total),
		Prods: prods,
	}
	return
}

// EditBom 编辑配置文件
func (s *Service) EditBom(ctx context.Context, req *inventory.EditBomRequest) (resp *inventory.DefaultResp, err error) {
	if err = s.downstream.CheckAdmin(ctx, req.WebToken, req.PStoreId); err != nil {
		return
	}
	var bomObject *prod.SelectionObjectView
	if len(req.Bom) > 0 {
		var objects []*prod.SelectionObjectView
		if objects, err = s.downstream.FetchObjects(
			ctx, req.WebToken.SgId, []string{fmt.Sprintf("%d-%d", req.ProdId, req.SkuId)}); err != nil {
			return
		}
		if len(objects) != 1 {
			err = constant.ErrObjectNotExist
			return
		}
		bomObject = objects[0]
	}
	err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		for _, editBom := range req.Bom {
			if editBom.Quantity <= 0 {
				err = ecode.BadRequest("物料数量必须大于0")
				return
			}
		}
		storageKey := fmt.Sprintf("%d-%d", req.ProdId, req.SkuId)
		var boms []*model.BomConfig
		if boms, err = s.dao.FindBomByStorageKeys(ctx, []string{storageKey}); err != nil {
			return
		}
		bomMap := make(map[uint64]float64)
		for _, bom := range boms {
			bomMap[bom.MaterialID] = bom.Quantity
		}
		if len(bomMap) == len(req.Bom) {
			for _, editBom := range req.Bom {
				if existQuantity, ok := bomMap[editBom.Id]; ok && existQuantity == editBom.Quantity {
					delete(bomMap, editBom.Id)
				}
			}
			if len(bomMap) == 0 {
				// 未进行任何修改
				return
			}
		}
		if len(req.Bom) > 0 && len(boms) == 0 {
			// 首次配置bom，商品触发盘空
			var storages []*model.Storage
			storages, err = s.dao.FindStoragesByStorageKeys(ctx, []string{storageKey})
			if len(storages) != 0 {
				// 首次配置bom，商品触发盘空（当前商品需要依赖bom关联数量来展示数据量，不需要仓库库存）
				var warehouses []*model.Warehouse
				warehouseIDMap := make(util.UInt32BoolMap)
				warehouseMap := make(map[uint32]*model.Warehouse)
				for _, storage := range storages {
					warehouseIDMap[storage.WarehouseID] = true
				}
				if warehouses, err = s.dao.FindWarehousesByIDs(
					ctx, warehouseIDMap.GetKeys(), req.WebToken.SgId); err != nil {
					return
				}
				for _, warehouse := range warehouses {
					warehouseMap[warehouse.ID] = warehouse
				}
				invalidWarehouseName := make(util.StringBoolMap)
				for _, storage := range storages {
					if storage.FreezeQuantity > 0 {
						invalidWarehouseName[warehouseMap[storage.WarehouseID].Name] = true
					}
				}
				if len(invalidWarehouseName) > 0 {
					err = ecode.AppendMessage(constant.ErrForbiddenAction,
						fmt.Sprintf("所选商品在仓库[%s]中存在冻结库存，请先处理未完结订单再初始化商品bom",
							strings.Join(invalidWarehouseName.GetKeys(), ",")))
					return
				}
				line := &inventory.InventoryLine{
					StorageType:   inventory.StorageType_PROD,
					StorageKey:    storageKey,
					InputQuantity: 0,
				}
				productRemarks := make(map[string]*model.FullRemark)
				for _, storage := range storages {
					// 创建盘点记录，清空对应库存数据
					if _, ok := productRemarks[storageKey]; !ok {
						// 初始化一次即可
						productRemarks[storageKey] = &model.FullRemark{
							SnapShotRemark: model.SnapShotRemark{
								Remark: model.Remark{
									Code: bomObject.Id,
									Name: bomObject.Name,
									Unit: bomObject.Unit,
								},
								Type: bomObject.TypeName,
							},
							StorageUnit:  bomObject.Unit,
							StorageRatio: 1,
							PStoreID:     bomObject.PStoreId,
						}
					}
					if _, err = s.addInventoryInternal(ctx, req.WebToken.SgId, warehouseMap[storage.WarehouseID],
						req.WebToken.EmpId, true, []*inventory.InventoryLine{line}, productRemarks,
						nil, "配置bom触发盘空"); err != nil {
						return
					}
				}
				if err = s.dao.DeleteStorageByStorageKey(ctx, storageKey); err != nil {
					// 删除实时库存记录
					return
				}
			}
		}
		now := time.Now()
		if len(boms) != 0 {
			// 先删除后插入更新
			if err = s.dao.DeleteBomByStorageKey(ctx, req.WebToken.SgId, storageKey, req.WebToken.EmpId, now); err != nil {
				return
			}
		}
		var isConfigured = false
		materialIDMap := make(util.UInt64BoolMap)
		if req.Bom != nil && len(req.Bom) > 0 {
			isConfigured = true
			var materials []*model.Material
			for _, bom := range req.Bom {
				materialIDMap[bom.Id] = true
			}
			if materials, err = s.dao.FindMaterialsByIDs(ctx, materialIDMap.GetKeys()); err != nil {
				return
			}
			materialMap := make(map[uint64]*model.Material)
			for _, material := range materials {
				if material.SgID != req.WebToken.SgId || material.PStoreID != req.PStoreId {
					err = constant.ErrMaterialNotExist
					return
				}
				materialMap[material.ID] = material
			}
			for _, b := range req.Bom {
				if _, ok := materialMap[b.Id]; !ok {
					err = constant.ErrMaterialNotExist
					return
				}
				bom := model.BomConfig{
					StorageKey:  storageKey,
					MaterialID:  b.Id,
					StorageType: uint8(inventory.StorageType_PROD),
					RefID:       req.ProdId,
					SgID:        req.WebToken.SgId,
					Quantity:    b.Quantity,
					Status:      constant.BomValid,
					Operator:    req.WebToken.EmpId,
					CreateTime:  now,
					UpdateTime:  now,
				}
				if err = s.dao.UpsertBom(ctx, &bom); err != nil {
					return
				}
			}
		}
		// 通知商品中心更新
		if err = s.downstream.UpdateProductBom(ctx, req.WebToken.SgId, req.PStoreId, req.ProdId, req.SkuId, isConfigured); err != nil {
			return
		}

		// 调整库存推送（这里不能使用通用方法）
		var (
			storages   []*model.Storage
			warehouses []*model.Warehouse
		)
		storageMap := make(map[uint32]model.StorageMap)
		if len(boms) > 0 {
			// 需要将历史bom也折算参与库存影响分析
			for _, bom := range boms {
				materialIDMap[bom.MaterialID] = true
			}
		}
		if len(materialIDMap) > 0 {
			warehouseIdMap := make(util.UInt32BoolMap)
			if storages, err = s.dao.FindStoragesByMaterialIDs(ctx, materialIDMap.GetKeys()); err != nil {
				return
			}
			for _, storage := range storages {
				if _, ok := storageMap[storage.WarehouseID]; !ok {
					storageMap[storage.WarehouseID] = make(model.StorageMap)
					warehouseIdMap[storage.WarehouseID] = true
				}
				storageMap[storage.WarehouseID][storage.StorageKey] = storage
			}
			if warehouses, err = s.dao.FindWarehousesByIDs(ctx, warehouseIdMap.GetKeys(), req.WebToken.SgId); err != nil {
				return
			}
			for _, warehouse := range warehouses {
				if warehouse.StoreID == 0 || warehouse.Status != constant.WarehouseEnable {
					continue
				}
				storageSubMap := storageMap[warehouse.ID]
				var (
					expectStorage int
					storageValid  bool
				)
				if warehouse.StorageMode != constant.WarehouseStorageModeStrong {
					// 非强管控模式，初始默认为无限量 / 否则为0库存
					expectStorage, storageValid = 9999, true
				}
				for _, bom := range req.Bom {
					bomStorageKey := fmt.Sprintf("m-%d", bom.Id)
					if storage, ok := storageSubMap[bomStorageKey]; ok {
						refAvailableQuantity := int((storage.Quantity - storage.FreezeQuantity) / bom.Quantity)
						if refAvailableQuantity < 0 {
							refAvailableQuantity = 0
						}
						if !storageValid {
							if refAvailableQuantity >= 9999 {
								refAvailableQuantity = 9999
							}
							expectStorage, storageValid = refAvailableQuantity, true
						} else if refAvailableQuantity < expectStorage {
							expectStorage = refAvailableQuantity
						}
					} else if warehouse.StorageMode == constant.WarehouseStorageModeStrong {
						// 强管控模式，任意bom不存在库存，视为清0库存
						expectStorage, storageValid = 0, false
						break
					}
				}
				//// 每个仓库仅推送当前商品即可
				//storagePayload, _ := json.Marshal(&model.StorageUpdatePayload{
				//	StoreID: warehouse.StoreID,
				//	Objects: []*model.StorageObject{
				//		{
				//			StorageKey:  storageKey,
				//			StorageType: int(inventory.StorageType_PROD),
				//			Stock:       expectStorage,
				//		},
				//	},
				//	Timestamp: int(now.Unix()),
				//})
				//if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicStorageUpdate, storagePayload); err != nil {
				//	return err
				//}
			}
		}
		return
	})
	resp = &inventory.DefaultResp{Status: true}
	return
}

//// GetBomStorageKeys
//func (s *Service) SyncBomToWebPos(ctx context.Context, req *inventory.BomSyncRequest) (resp *inventory.DefaultResp, err error) {
//	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
//		for _, sgID := range req.SgIds {
//			var storageKeys []string
//			if storageKeys, err = s.dao.FindBomStorageKeysBySgID(ctx, sgID); err != nil {
//				return
//			}
//			// 发送事务消息
//			bomSyncPayload, _ := json.Marshal(&model.BomSyncPayload{
//				SgID:        sgID,
//				StorageKeys: storageKeys,
//			})
//			if err = s.dao.PublishRepeatedNSQMessage(
//				ctx, constant.TopicBomSync, strconv.Itoa(int(sgID)), bomSyncPayload); err != nil {
//				return
//			}
//		}
//		return
//	}); err != nil {
//		return
//	}
//	resp = &inventory.DefaultResp{
//		Status: true,
//	}
//	return
//}
